package com.smsc.headend.task.engine.handler.impl.timesync;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.smsc.headend.common.enums.errorcode.TaskHandlerErrorCode;
import com.smsc.headend.common.utils.CosemUtils;
import com.smsc.headend.common.utils.RedisKeys;
import com.smsc.headend.common.utils.RedisUtils;
import com.smsc.headend.module.data.enums.OperationGroup;
import com.smsc.headend.module.protocol.DeviceData;
import com.smsc.headend.module.protocol.ansi.AnsiData;
import com.smsc.headend.module.protocol.cosem.bo.CosemData;
import com.smsc.headend.module.report.entity.LogScheduleTaskDevice;
import com.smsc.headend.module.report.entity.LogSyncTimeHis;
import com.smsc.headend.module.task.dto.AtomicTask;
import com.smsc.headend.module.task.dto.Task;
import com.smsc.headend.module.task.enums.TaskType;
import com.smsc.headend.task.engine.dao.TaskDAO;
import com.smsc.headend.task.engine.exception.UDISTaskHandlerException;
import com.smsc.headend.task.engine.handler.TaskHandler;
import com.smsc.headend.task.engine.service.CosemXmlService;
import com.smsc.headend.task.engine.service.report.TimeSyncReportService;
import com.smsc.headend.task.engine.service.task.LogScheduleTaskDeviceService;
import com.smsc.headend.task.engine.service.task.TaskService;
import com.smsc.headend.task.engine.utils.AnsiUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * @author liangli
 * @date 2020/10/16
 */
@Slf4j
@Service
public class ReadClockHandler implements TaskHandler {
    public static final String DEVICE_TIME = "deviceTime";
    public static final String SYSTEM_TIME = "systemTime";
    public static final String AFTER_DEVICE_TIME = "afterDeviceTime";
    public static final String AFTER_SYSTEM_TIME = "afterSystemTime";
    public static final String CONNECT_FAIL_REGEX = "^build+.*fail$";
    @Autowired
    CosemXmlService cosemXmlService;
    @Autowired
    TaskDAO taskDAO;
    @Autowired
    TimeSyncReportService timeSyncReportService;
    @Autowired
    TaskService taskService;
    @Autowired
    RedisUtils redisUtils;
    @Autowired
    LogScheduleTaskDeviceService logScheduleTaskDeviceService;

    @Override
    public int handle(Task task, AtomicTask atomicTask, DeviceData deviceData, String actionResult) throws UDISTaskHandlerException {
        if (!(deviceData instanceof CosemData) && !(deviceData instanceof AnsiData)) {
            throw new UDISTaskHandlerException(TaskHandlerErrorCode.DEVICE_DATA_TYPE_NOT_SUPPORT.toString());
        }
        log.debug("ReadClockHandler deviceData={}", JSONUtil.toJsonStr(deviceData));
        Date date;
        if (deviceData instanceof AnsiData) {
            AnsiData ansiData = (AnsiData) deviceData;
            String timeString = AnsiUtil.parseDateTimeString(ansiData.getAnsiByteBuffer());
            date = DateUtil.parseDateTime(timeString);
        } else {
            CosemData cosemData = (CosemData) deviceData;
            String timeString = (String) cosemXmlService.getData(cosemData.getType(), cosemData.getValue());
            date = CosemUtils.parseOctStringDataTime(timeString);
        }

        Map<String, Object> map = getParameterMap(task, atomicTask);
        Integer limitLower = Convert.toInt(map.get("lowerLimit"));
        Integer upperLimit = Convert.toInt(map.get("upperLimit"));
        Long userId = Convert.toLong(map.get("userId"));

        handleTimeDiff(task, atomicTask, limitLower, upperLimit, date, userId);
        return 1;
    }

    @Override
    public int handleFailure(Task task, AtomicTask atomicTask, String message) {
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), "message", message, 3600L, true);
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), "result", 2, 3600L, true);
        if (task.getTaskType().equalsIgnoreCase(TaskType.System_Time_Sync.toString())) {
            int failedCode = LogScheduleTaskDevice.StatusDescEnum.DecodeDataFailed.getCode();
            if (StrUtil.isNotEmpty(message)) {
                if (Pattern.matches(CONNECT_FAIL_REGEX, message)) {
                    failedCode = LogScheduleTaskDevice.StatusDescEnum.DeviceAuthenticationFailed.getCode();
                }

            }
            logScheduleTaskDeviceService.sendMessage(task, LogScheduleTaskDevice.StatusEnum.Failed.getCode(), failedCode);
        }
        return -1;
    }

    private void handleTimeDiff(Task task, AtomicTask atomicTask, Integer limitLower, Integer upperLimit, Date deviceTime, Long userId) {
        Long currentTimeSeconds = SystemClock.now() / 1000;
        Long clockTime = deviceTime.getTime() / 1000;
        Long timeDiff = clockTime - currentTimeSeconds;
        log.debug("TimeSync: handleTimeDiff, group={}", OperationGroup.forId(atomicTask.getGroupId().intValue()).getName());
        //人工Get
        if (OperationGroup.ManualTimeSyncRead.getId().longValue() == atomicTask.getGroupId()) {
            handlerManualReadClock(task, atomicTask, clockTime, currentTimeSeconds, deviceTime, timeDiff, userId);
            return;
        }
        //人工Set
        if (OperationGroup.ManualTimeSyncWrite.getId().longValue() == atomicTask.getGroupId()) {
            handlerManualSetReadClock(task, atomicTask, clockTime, currentTimeSeconds, timeDiff, userId);
            return;
        }
        //人工Sync和系统Sync
        if (OperationGroup.TimeSync.getId().longValue() == atomicTask.getGroupId()) {
            handleSystemTimeSyncRead(task, atomicTask, limitLower, upperLimit, clockTime, currentTimeSeconds, timeDiff, userId);
            if (task.getTaskType().equalsIgnoreCase(TaskType.System_Time_Sync.toString())) {
                if (isSubTaskGroupLastAtomicTask(task, atomicTask)) {
                    logScheduleTaskDeviceService.sendMessage(task, LogScheduleTaskDevice.StatusEnum.Success.getCode(), LogScheduleTaskDevice.StatusDescEnum.CollectedSuccess.getCode());
                }
            }
        }
    }

    private void handlerManualSetReadClock(Task task, AtomicTask atomicTask, Long deviceTime, Long systemTime, Long timeDiff, Long userId) {
        Integer atomicSeq = atomicTask.getSeq();
        if (atomicSeq == 0) {
            manualSetReadClockFirstTime(task, atomicTask, deviceTime, systemTime);
        } else if (atomicSeq == 2) {
            manualSetReadClockSecondTime(task, atomicTask, deviceTime, systemTime, timeDiff, userId);
        }
    }

    private void manualSetReadClockFirstTime(Task task, AtomicTask atomicTask, Long deviceTime, Long systemTime) {
        //Redis记录对时之前的设备时间
        redisUtils.set(RedisKeys.getDeviceTimeBefore(task.getTaskNo()), deviceTime);
        log.debug("manual read before set: deviceTime={}, systemTime={}", deviceTime, systemTime);
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), DEVICE_TIME, deviceTime, 3600L, true);
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), SYSTEM_TIME, systemTime, 3600L, true);
    }

    private void manualSetReadClockSecondTime(Task task, AtomicTask atomicTask, Long deviceTime, Long systemTime, Long timeDiff, Long userId) {
        log.debug("manual read after set: deviceTime={}, systemTime={}", deviceTime, systemTime);
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), AFTER_DEVICE_TIME, deviceTime, 3600L, true);
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), AFTER_SYSTEM_TIME, systemTime, 3600L, true);
        Long syncTime = Long.parseLong(StrUtil.toString(redisUtils.get(RedisKeys.getClockSyncTime(task.getTaskNo()))));
        Long deviceTimeBefore = Long.parseLong(StrUtil.toString(redisUtils.get(RedisKeys.getDeviceTimeBefore(task.getTaskNo()))));
        Long timeDiffBefore = deviceTimeBefore - systemTime;
        Long deviceId = taskService.getDeviceId(task);
        Long deviceType = taskService.getDeviceType(task);
        Map param = getParameterMap(task, atomicTask);
        Boolean isSetSuccess = false;
        if (param.containsKey("targetTv")) {
            Long targetTv = Convert.toLong(param.get("targetTv"));
            Long diffFromTargetTv = deviceTime - targetTv;
            log.debug("Set time diff from target tv: {}s", diffFromTargetTv);
            isSetSuccess = Math.abs(diffFromTargetTv) < 5;
        }
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), "result", isSetSuccess ? 1 : 2, 3600L, true);
        timeSyncReportService.saveReport(deviceId, deviceType, syncTime
                , isSetSuccess ? LogSyncTimeHis.SyncResult.SYNC_SUCCESS.getCode() : LogSyncTimeHis.SyncResult.SYNC_FAIL.getCode()
                , deviceTime, systemTime, timeDiffBefore, timeDiff, userId, 1);
    }

    private void handleSystemTimeSyncRead(Task task, AtomicTask atomicTask, Integer limitLower, Integer upperLimit, Long deviceTime, Long currentTimeSeconds, Long timeDiff, Long userId) {
        Integer atomicSeq = atomicTask.getSeq();
        Boolean outOfSyncRange = Math.abs(timeDiff) < limitLower || (upperLimit != null && Math.abs(timeDiff) > upperLimit);
        log.debug("time sync: out of sync range {}, limitLower={}, upperLimit={}", outOfSyncRange, limitLower, upperLimit);
        if (atomicSeq == 0) {
            preCheckTimeHandler(task, atomicTask, outOfSyncRange, limitLower, upperLimit, deviceTime, timeDiff, userId);
        } else if (atomicSeq == 2) {
            postCheckTimeHandler(task, atomicTask, deviceTime, currentTimeSeconds, timeDiff, userId);
        }
    }

    private void handlerManualReadClock(Task task, AtomicTask atomicTask, Long deviceTime, Long systemTime, Date deviceDate, Long timeDiff, Long userId) {
        log.debug("manual read: deviceTime={}, systemTime={}", deviceTime, systemTime);
        String deviceKey = DEVICE_TIME;
        String systemKey = SYSTEM_TIME;
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), deviceKey, deviceTime, 3600L, true);
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), systemKey, systemTime, 3600L, true);
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), "result", 1, 300L, true);

        Long timeDiffBefore = timeDiff;
        Long deviceId = taskService.getDeviceId(task);
        Long deviceType = taskService.getDeviceType(task);
        Long serverTime = System.currentTimeMillis() / 1000;
        timeSyncReportService.saveReport(deviceId, deviceType, null
                , LogSyncTimeHis.SyncResult.NO_SYNC.getCode()
                , deviceDate.getTime() / 1000, serverTime, timeDiffBefore, null, userId, null);

    }

    private void postCheckTimeHandler(Task task, AtomicTask atomicTask, Long deviceTime, Long serverTime, Long timeDiff, Long userId) {
        Long syncTime = Long.parseLong(StrUtil.toString(redisUtils.get(RedisKeys.getClockSyncTime(task.getTaskNo()))));
        Long deviceTimeBefore = Long.parseLong(StrUtil.toString(redisUtils.get(RedisKeys.getDeviceTimeBefore(task.getTaskNo()))));
        Long timeDiffBefore = deviceTimeBefore - serverTime;
        Long deviceId = taskService.getDeviceId(task);
        Long deviceType = taskService.getDeviceType(task);
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), AFTER_DEVICE_TIME, deviceTime, 3600L, true);
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), AFTER_SYSTEM_TIME, serverTime, 3600L, true);
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), "result", 1, 300L, true);

        //判断同步源 null：Get，0=系统对时，1=人工Set,2=人工Sync
        Integer syncSrc;
        if (task.getTaskType().equalsIgnoreCase(TaskType.Manual_Time_Sync.toString())) {
            syncSrc = 2;
        } else {
            syncSrc = 0;
        }
        timeSyncReportService.saveReport(deviceId, deviceType, syncTime
                , Math.abs(timeDiff) < 2 ? LogSyncTimeHis.SyncResult.SYNC_SUCCESS.getCode() : LogSyncTimeHis.SyncResult.SYNC_FAIL.getCode()
                , deviceTime, serverTime, timeDiffBefore, timeDiff, userId, syncSrc);

    }

    private void preCheckTimeHandler(Task task, AtomicTask atomicTask, Boolean outOfSyncRange, Integer limitLower, Integer upperLimit, Long deviceTime, Long timeDiff, Long userId) {
        //保存第一次读取的时间到Redis
        Long serverTime = SystemClock.now() / 1000;
        //Redis记录对时之前的设备时间
        redisUtils.set(RedisKeys.getDeviceTimeBefore(task.getTaskNo()), deviceTime);
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), DEVICE_TIME, deviceTime, 3600L, true);
        taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), SYSTEM_TIME, serverTime, 3600L, true);
        if (outOfSyncRange) {
            log.info("TimeSync: sync out of range， taskNo={}, comId={} , meterId={}", task.getTaskNo(), task.getComId(), task.getMeterId());
            if (task.getTaskType().equalsIgnoreCase(TaskType.System_Time_Sync.toString())) {
                logScheduleTaskDeviceService.sendMessage(task, LogScheduleTaskDevice.StatusEnum.Success.getCode(), LogScheduleTaskDevice.StatusDescEnum.CollectedSuccess.getCode());
            }
            removeSetAtomicTaskIfExists(task, atomicTask);
            Long timeDiffBefore = Convert.toLong(timeDiff);
            Long deviceId = taskService.getDeviceId(task);
            Long deviceType = taskService.getDeviceType(task);
            if (task.getTaskType().equalsIgnoreCase(TaskType.Manual_Time_Sync.toString())) {
                //手工对时 记录系统时间 以及 no action状态
                log.debug("manual time sync : out of sync range {}", outOfSyncRange);
                taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), "result", 0, 300L, true);
            }
            Integer syncSrc;
            if (task.getTaskType().equalsIgnoreCase(TaskType.Manual_Time_Sync.toString())) {
                syncSrc = 2;
            } else {
                syncSrc = 0;
            }
            timeSyncReportService.saveReport(deviceId, deviceType, null
                    , LogSyncTimeHis.SyncResult.NO_SYNC.getCode()
                    , deviceTime, serverTime, timeDiffBefore, null, userId, syncSrc);
        } else {
            taskDAO.addTaskData(task.getTaskNo(), atomicTask.getAtomicTaskNo(), "timeDiffBefore", timeDiff, 300L, null);
        }
    }

    private void removeSetAtomicTaskIfExists(Task task, AtomicTask atomicTask) {
        if (atomicTask.getGroupId().longValue() == OperationGroup.TimeSync.getId()) {
            //pop set clock atomic task
            AtomicTask setClockTask = taskDAO.atomicTaskQueuePopReverse(task.getTaskNo());
            log.info("remove setClock atomicTask {} ", setClockTask.getAtomicTaskNo());
            AtomicTask validClockTask = taskDAO.atomicTaskQueuePopReverse(task.getTaskNo());
            log.info("remove validClock atomicTask {} ", validClockTask.getAtomicTaskNo());
        }
    }
}
